namespace SimpleWebServices.Reflection
{
    using System;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Xml;
    using System.Xml.XPath;

    public static class ReflectionExtensionMethods
    {
        public static IXPathNavigable ToXml(this object value)
        {
            IXPathNavigable result = null;

            if (null != value)
            {
                using (StringWriter text = new StringWriter(CultureInfo.InvariantCulture))
                {
                    using (XmlTextWriter xml = new XmlTextWriter(text))
                    {
                        xml.WriteStartElement(value.GetType().Name.Replace('`', '-'));
                        Write(xml, value, new Collection<object>());
                        xml.WriteEndElement();

                        xml.Flush();
                        text.Flush();

                        XmlDocument document = new XmlDocument();
                        document.CreateXmlDeclaration("1.0", "utf-8", null);
                        string temp = text.ToString();
                        document.LoadXml(temp);
                        result = document;
                    }
                }
            }

            return result;
        }

        private static void Write(XmlTextWriter xml, object value, Collection<object> done)
        {
            if (null != value)
            {
                if (value is IConvertible)
                {
                    xml.WriteString(value.ToXmlString());
                }
                else if (!done.Contains(value))
                {
                    done.Add(value);
                    if (WriteElement(xml, value as NameValueCollection))
                    {
                    }
                    else
                    {
                        PropertyInfo[] properties = value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
                        WriteAttributes(xml, value, properties);
                        WriteElements(xml, value, properties, done);
                        Write(xml, value as IEnumerable, done);
                    }
                }
            }
        }

        private static void Write(XmlTextWriter xml, IEnumerable value, Collection<object> done)
        {
            if (null != value)
            {
                foreach (var item in value as IEnumerable)
                {
                    xml.WriteStartElement(item.GetType().Name.Replace('`', '-'));
                    Write(xml, item, done);
                    xml.WriteEndElement();
                }
            }
        }

        private static void WriteAttributes(XmlTextWriter xml, object value, PropertyInfo[] properties)
        {
            foreach (PropertyInfo property in properties)
            {
                if (property.CanRead)
                {
                    ParameterInfo[] parameters = property.GetGetMethod(false).GetParameters();
                    if (null == parameters || 0 == parameters.Length)
                    {
                        try
                        {
                            object x = property.GetValue(value, null);
                            if (x is IConvertible)
                            {
                                xml.WriteAttributeString(property.Name, x.ToXmlString());
                            }
                        }
                        catch (TargetInvocationException exception)
                        {
                            xml.WriteAttributeString(property.Name, exception.Message);
                        }
                    }
                }
            }
        }

        private static bool WriteElement(XmlTextWriter xml, NameValueCollection value)
        {
            bool result = false;

            if (null != value)
            {
                result = true;
                xml.WriteAttributeString("Count", value.Count.ToXmlString());
                if (0 != value.Count)
                {
                    for (int i = 0; i < value.Count; i++)
                    {
                        xml.WriteStartElement("Value");
                        xml.WriteAttributeString("Name", value.Keys[i]);
                        xml.WriteString(value[i]);
                        xml.WriteEndElement();
                    }
                }
            }

            return result;
        }

        private static void WriteElements(XmlTextWriter xml, object value, PropertyInfo[] properties, Collection<object> done)
        {
            foreach (PropertyInfo property in properties)
            {
                if (property.CanRead)
                {
                    ParameterInfo[] parameters = property.GetGetMethod(false).GetParameters();
                    if (null == parameters || 0 == parameters.Length)
                    {
                        try
                        {
                            object x = property.GetValue(value, null);
                            if (x is IConvertible)
                            {
                            }
                            else if (null != x)
                            {
                                xml.WriteStartElement(property.Name);
                                Write(xml, x, done);
                                xml.WriteEndElement();
                            }
                        }
                        catch (TargetInvocationException exception)
                        {
                            xml.WriteElementString(property.Name, exception.Message);
                        }
                    }
                }
            }
        }
    }
}